/** @file sdio.c
  *
  * @brief This file contains the sdio device function calls
  *
  *  Copyright (C) 2024, Artosyn Technologies. ALL RIGHTS RESERVED.
  *
  *  This Information is proprietary to Artosyn Technologies and MAY NOT
  *  be copied by any method or incorporated into another program without
  *  the express written consent of Artosyn. This Information or any portion
  *  thereof remains the property of Artosyn. The Information contained herein
  *  is believed to be accurate and Artosyn assumes no responsibility or
  *  liability for its use in any way and conveys no license or title under
  *  any patent or copyright and makes no representation or warranty that this
  *  Information is free from patent or copyright infringement.
  *
***/

/****************************
 * Include
 ****************************/
#include <linux/kernel.h>
#include <linux/netdevice.h>
#include <linux/module.h>
#include <linux/firmware.h>
#include <linux/vmalloc.h>
#include <linux/mmc/core.h>
#include <linux/mmc/card.h>
#include <linux/mmc/sdio_func.h>
#include <linux/mmc/sdio_ids.h>
#include <linux/mmc/host.h>
#include <linux/mmc/sdio.h>

#include "bus/sdio.h"
#include "utils/utils.h"
#include "rpc/ar_rpc.h"
#include "oal_main.h"
#include "oal_workqueue.h"
#include "oal_dnld.h"


/****************************
 * Local Definition
 ****************************/

/** SDIO info of artosyn romcode */
#define ARTO_ROMCODE_VID        (0x4152)
#define ARTO_ROMCODE_PID        (0x8030)
#define ARTO_ROMCODE_NAME       "Rom_sdio"

/** USB info of artosyn rtos */
#define ARTO_RTOS_VID           (0x4152)
#define ARTO_RTOS_PID           (0x8031)
#define ARTO_RTOS_NAME          "Artosyn in HS Mode"

/** The number of times to try when polling for status bits */
#define MAX_POLL_TRIES            10

/** sleep time before read */
int rx_sleep_us = 0;
/** sleep time before write */
int tx_sleep_us = 0;
/** rom mode test */
bool rom_mode_test = 0;
/** rx TP test mode */
bool sdio_rx_tp_test = 0;
/** sdio tx TP test mode */
bool sdio_tx_tp_test = 0;
/** sdio tx TP test buf len */
int sdio_tx_tp_test_len = 4000;
/** sdio tp test stats thread */
oal_thread sdio_tp_test_stats_thread;
/** sdio tx tp test thread */
oal_thread sdio_tx_tp_test_thread;

struct artosyn_sdio_device {
    oal_handle *handle;                     /** Point to oal handler */

    struct sdio_func *func;
    char tx_buf[SDIO_TRANS_MAX_SZIE];
    char rx_buf[SDIO_TRANS_MAX_SZIE];
    volatile unsigned int reading;
    volatile unsigned int read_offset;
    volatile unsigned int read_valid_size;
    volatile unsigned int read_size;
    volatile unsigned char reserved[64];
    volatile unsigned int writing;
    volatile unsigned int write_offset;
    volatile unsigned int write_valid_size;
    volatile unsigned int write_size;
    unsigned char message[SDIO_MAILBOX_CHANNEL_COUNT];
    unsigned char msg_valid[SDIO_MAILBOX_CHANNEL_COUNT];
    wait_queue_head_t mailbox_q;
    wait_queue_head_t tx_q;
    wait_queue_head_t rx_q;

    /** reg check workqueue */
    void *reg_workqueue;     /** refers to oal_workqueue */
    void *rx_workqueue;      /** refers to oal_workqueue */

    bool rom_mode;
};

/** Handle data structure for driver  */
typedef struct artosyn_sdio_device artosyn_sdio_device_t;

/****************************
 * Function declare
 ****************************/
static int sdio_artosyn_probe(struct sdio_func *func,
        const struct sdio_device_id *id);
static void sdio_artosyn_remove(struct sdio_func *func);
static ssize_t artosyn_sdio_write(struct artosyn_sdio_device *dev, uint8_t* buf, size_t count, bool non_block);

static void
ar_sdio_update_card_type(oal_handle *handle, void *card);
static oal_status
ar_sdio_send_dnld_req(IN oal_handle *handle, u32 addr, u8* data, u32 len, u16 timeout);
static oal_status
ar_sdio_send_dnld_done(IN oal_handle *handle, u16 timeout);
static oal_status
ar_sdio_recv_packet_complete(IN oal_handle *poal_handle,
                                      IN struct ar_skbreq *req,
                                      IN oal_status status);
static oal_status
ar_sdio_write_data_sync(oal_handle *handle, IN struct ar_skbreq *req);
static oal_status
ar_sdio_read_data_sync(oal_handle *handle, struct ar_skbreq *req);
/****************************
 * Local Parameters
 ****************************/
/** oal_callbacks */
static oal_callbacks oal_cb = {
    // .oal_alloc_skbreq_ex = ar_usb_alloc_skbreq_ex,
    .oal_update_card_type = ar_sdio_update_card_type,
    .oal_send_dnld_req = ar_sdio_send_dnld_req,
    .oal_send_dnld_done = ar_sdio_send_dnld_done,
    // .oal_try_recv_pend = ar_usb_try_recv_pend,
    .oal_recv_packet_complete = ar_sdio_recv_packet_complete,
    .oal_write_data_sync = ar_sdio_write_data_sync,
    // .oal_write_data_async = ar_usb_write_data_async,
    .oal_read_data_sync = ar_sdio_read_data_sync,
    // .oal_read_data_async = ar_usb_read_data_async,
};

/*-------------------SDIO ID list-------------------*/
static const struct sdio_device_id sdio_artosyn_ids[] = {
    { SDIO_DEVICE(ARTO_ROMCODE_VID, ARTO_ROMCODE_PID)},
    { SDIO_DEVICE(ARTO_RTOS_VID,    ARTO_RTOS_PID)   },
    { /* end: all zeroes */                },
};

MODULE_DEVICE_TABLE(sdio, sdio_artosyn_ids);

static struct sdio_driver sdio_artosyn_driver = {
    .probe        = sdio_artosyn_probe,
    .remove        = sdio_artosyn_remove,
    .name        = "sdio_artosyn",
    .id_table    = sdio_artosyn_ids,
};

/* stats */
static uint64_t total_tx_pkts = 0;
static uint64_t total_tx_bytes = 0;
static uint64_t total_rx_pkts = 0;
static uint64_t total_rx_bytes = 0;

static uint64_t total_rx_isr = 0;
static uint64_t total_rx_process = 0;
static uint64_t total_tx_isr = 0;

static DEFINE_MUTEX(artosyn_mutex);
static volatile uint8_t removed = 1;
static volatile uint32_t opened = 0;

static uint64_t sdio_tx_bytes_period = 0;
static uint64_t sdio_tx_pkts_period = 0;
static uint64_t sdio_rx_bytes_period = 0;
static uint64_t sdio_rx_pkts_period = 0;

/****************************
 * Function definition
 ****************************/

int sdio_auto_scan_enable(struct sdio_func *func, bool en)
{
    struct mmc_host *host = NULL;
    struct mmc_card *mmc_cardp = NULL;

    if (!func) {
        AR_DBG(DM_SDIO, DL_ERR, "%s: func is NULL.\n", __FUNCTION__);
        return -1;
    }

    mmc_cardp = func->card;
    if (!mmc_cardp) {
        AR_DBG(DM_SDIO, DL_ERR, "%s: mmc_cardp is NULL.\n", __FUNCTION__);
        return -1;
    }

    host = mmc_cardp->host;

    if (!host) {
        AR_DBG(DM_SDIO, DL_ERR, "%s: mmc host is NULL.\n", __FUNCTION__);
        return -1;
    }

    /* 5. Enable the Removable capbility of the mmc and autoscan */
    if (en) {
        host->caps |= MMC_CAP_NEEDS_POLL;
        host->caps &= ~MMC_CAP_NONREMOVABLE;
    }
    else {
        host->caps &= ~MMC_CAP_NEEDS_POLL;
        host->caps |= MMC_CAP_NONREMOVABLE;
    }

    /* 6. Detect the change of mmc device and trigger the scan poll */
    if (host->ops && host->ops->card_event) {
        host->ops->card_event(host);
    }

    mmc_detect_change(host, msecs_to_jiffies(200));

    return 0;
}

/**
 *  @brief This function updates card reg based on the Cmd52 value in dev structure
 *
 *  @param dev   A pointer to driver_handle structure
 *  @param func     A pointer to store func variable
 *  @param reg      A pointer to store reg variable
 *  @param val      A pointer to store val variable
 *  @return         0 or fail otherwise
 */
int
sdio_read_write_cmd52(struct artosyn_sdio_device *dev, unsigned char write, unsigned char func, int addr, unsigned char *val)
{
    int ret = 0;

    ENTER();

    if (dev == NULL) {
        AR_DBG(DM_SDIO, DL_ERR, "%s: dev is NULL!\n", __func__);
        return -EINVAL;
    }
    sdio_claim_host(dev->func);
    if (write) {
        /* Perform actual write only if val is provided */
        if (func)
            sdio_writeb(dev->func, *val, addr, &ret);
        else
            sdio_f0_writeb(dev->func, *val, addr, &ret);
        if (ret) {
            AR_DBG(DM_SDIO, DL_ERR,
                   "%s: Cannot write value (0x%x) to func %d reg 0x%x\n", __func__,
                   *val, func, addr);
        } else {
            AR_DBG(DM_SDIO, DL_VRB, "%s: [OK]Write value (0x%x) to func %d reg 0x%x\n", __func__,
                   (unsigned char)*val, func, addr);
        }
    } else {
        if (func)
            *val = sdio_readb(dev->func, addr, &ret);
        else
            *val = sdio_f0_readb(dev->func, addr, &ret);
        if (ret) {
            AR_DBG(DM_SDIO, DL_ERR,
                   "%s: Cannot read value from func %d reg 0x%x\n", __func__,
                   func, addr);
        } else {
            AR_DBG(DM_SDIO, DL_VRB,
                   "%s: read value (0x%x) from func %d reg 0x%x\n", __func__,
                   (unsigned char)*val, func, addr);
        }
    }
    sdio_release_host(dev->func);

    LEAVE();

    return ret;
}

/**
 *  @brief This function gets sdio cccr regs value
 *
 *  @param dev     A pointer to driver_handle structure
 *
 *  @param func_num    function number
 *
 *  @param addr        SDIO reg addr
 *
 *  @param val         CCCR reg values
 *
 *  @return        0 or fail otherwise
 */
int sdio_get_cccr_value(struct artosyn_sdio_device * dev, unsigned char func_num, unsigned long addr, unsigned char *val)
{
    int ret = 0;
    unsigned long tries;
    unsigned char reg_data = 0;

    ENTER();

    if ((dev == NULL) || (func_num > SDIO_FUNC_MAX)) {
        AR_DBG(DM_SDIO, DL_ERR, "%s: driver dev is null or invalid function number: %d\n", __FUNCTION__, func_num);
        return -ENODEV;
    }

    /*CMD52: get cccr reg value*/
    for (tries = 0; tries < MAX_POLL_TRIES; tries++) {
        ret = sdio_read_write_cmd52(dev, SDIO_ACT_READ, func_num, addr, &reg_data);
        if (ret < 0) {
            udelay(100);
            continue;
        } else {
            *val = reg_data;
            AR_DBG(DM_SDIO, DL_INF, "Succeed to get cccr value = 0x%x\n", *val);
            break;
        }
    }

    LEAVE();
    return ret;
}

/*fw dnld*/
static uint32_t sdio_rom_send(artosyn_sdio_device_t * dev, char *data, uint32_t *offset, unsigned int dest_addr, int send_length)
{
    int sent = -1;
    int ret = -1;
    uint32_t read_offset = 0;
    uint32_t read_length = 0;
    uint32_t len = 0;
    uint32_t buf_offset = 0;
    uint8_t *tx_buffer = NULL;
    uint8_t *rx_buffer = NULL;

    uint64_t start_address = dest_addr;
    uint16_t *p_len = NULL;
    uint64_t *p_dest_addr = NULL;

    tx_buffer = kzalloc(SDIO_BOOT_ONESHOT_MAX_SIZE, GFP_KERNEL);
    if (!tx_buffer) {
        AR_DBG(DM_SDIO, DL_ERR, "tx_buffer malloc failed!");
        return 0;
    }

    rx_buffer = kzalloc(SDIO_BOOT_ONESHOT_MAX_SIZE, GFP_KERNEL);
    if (!rx_buffer) {
        kfree(tx_buffer);
        AR_DBG(DM_SDIO, DL_ERR, "rx_buffer malloc failed!");
        return 0;
    }

    p_len = (uint16_t*)&tx_buffer[2];
    p_dest_addr = (uint64_t *)&tx_buffer[4];

    memset(tx_buffer, 0, SDIO_BOOT_ONESHOT_MAX_SIZE);
    memset(rx_buffer, 0, SDIO_BOOT_ONESHOT_MAX_SIZE);

    tx_buffer[0] = 'S';
    tx_buffer[1] = 'D';

    if (offset) {
        buf_offset = *offset;
    }

    if(send_length == 0)
    {
        // mutex_lock(&artosyn_mutex);
        // memcpy(dev->tx_buf, tx_buffer, SDIO_BOOT_PKG_HEADER_SIZE);
        // mutex_unlock(&artosyn_mutex);
        sent = artosyn_sdio_write(dev, tx_buffer, SDIO_BOOT_PKG_HEADER_SIZE, false);
        if(sent != SDIO_BOOT_PKG_HEADER_SIZE)
        {
            AR_DBG(DM_SDIO, DL_ERR, "ending package send error %d\n", sent);
        }
        kfree(tx_buffer);
        kfree(rx_buffer);
        return 0;
    }

    while(read_length < send_length)
    {
        read_offset = 0;
        len = send_length-read_length >= SDIO_BOOT_ONESHOT_MAX_SIZE-SDIO_BOOT_PKG_HEADER_SIZE ? SDIO_BOOT_ONESHOT_MAX_SIZE-SDIO_BOOT_PKG_HEADER_SIZE : send_length-read_length;
        memcpy(rx_buffer, data + buf_offset, len);
        buf_offset += len;
        ret = len;
        if(ret > 0)
        {
            if(ret == SDIO_BOOT_ONESHOT_MAX_SIZE-SDIO_BOOT_PKG_HEADER_SIZE)
            {
                *p_len = ret;
                *p_dest_addr = start_address;
                memcpy(tx_buffer+SDIO_BOOT_PKG_HEADER_SIZE, rx_buffer, ret);
                // mutex_lock(&artosyn_mutex);
                // memcpy(dev->tx_buf, tx_buffer, ret+SDIO_BOOT_PKG_HEADER_SIZE);
                // mutex_unlock(&artosyn_mutex);
                sent = artosyn_sdio_write(dev, tx_buffer, ret+SDIO_BOOT_PKG_HEADER_SIZE, false);
                if(sent != ret+SDIO_BOOT_PKG_HEADER_SIZE)
                {
                    AR_DBG(DM_SDIO, DL_ERR, "uploading error, read_length %d, send_length = %d, ret = %d, sent = %d\n", read_length, send_length, ret, sent);
                    break;
                }
                read_length += ret;
                start_address += ret;
            }
            else
            {
                int residue = (ret+SDIO_BOOT_PKG_HEADER_SIZE) % SDIO_DEVICE_BLOCK_SIZE;

                if(ret+SDIO_BOOT_PKG_HEADER_SIZE >= SDIO_DEVICE_BLOCK_SIZE)
                {
                    *p_len = ret-residue;
                    *p_dest_addr = start_address;
                    memcpy(tx_buffer+SDIO_BOOT_PKG_HEADER_SIZE, rx_buffer, ret-residue+SDIO_BOOT_PKG_HEADER_SIZE);
                    // mutex_lock(&artosyn_mutex);
                    // memcpy(dev->tx_buf, tx_buffer, ret-residue+SDIO_BOOT_PKG_HEADER_SIZE);
                    // mutex_unlock(&artosyn_mutex);
                    sent = artosyn_sdio_write(dev, tx_buffer, ret-residue+SDIO_BOOT_PKG_HEADER_SIZE, false);
                    if(sent != ret-residue+SDIO_BOOT_PKG_HEADER_SIZE)
                    {
                        AR_DBG(DM_SDIO, DL_ERR, "uploading block error, read_length %d, send_length = %d, ret = %d, sent = %d, residue = %d\n", read_length, send_length, ret, sent, residue);
                        break;
                    }
                    read_length += ret-residue;
                    start_address += ret-residue;
                    read_offset += ret-residue;
                }

                if(residue+SDIO_BOOT_PKG_HEADER_SIZE > SDIO_DEVICE_BLOCK_SIZE)
                {
                    *p_len = SDIO_DEVICE_BLOCK_SIZE-SDIO_BOOT_PKG_HEADER_SIZE;
                    *p_dest_addr = start_address;
                    memcpy(tx_buffer+SDIO_BOOT_PKG_HEADER_SIZE, rx_buffer+read_offset, SDIO_DEVICE_BLOCK_SIZE-SDIO_BOOT_PKG_HEADER_SIZE);
                    // mutex_lock(&artosyn_mutex);
                    // memcpy(dev->tx_buf, tx_buffer, SDIO_DEVICE_BLOCK_SIZE-SDIO_BOOT_PKG_HEADER_SIZE);
                    // mutex_unlock(&artosyn_mutex);
                    sent = artosyn_sdio_write(dev, tx_buffer, SDIO_DEVICE_BLOCK_SIZE-SDIO_BOOT_PKG_HEADER_SIZE, false);
                    if(sent != SDIO_DEVICE_BLOCK_SIZE-SDIO_BOOT_PKG_HEADER_SIZE)
                    {
                        AR_DBG(DM_SDIO, DL_ERR, "uploading residue 1 error, read_length %d, send_length = %d, ret = %d, sent = %d, residue = %d\n", read_length, send_length, ret, sent, residue);
                        break;
                    }

                    read_length += SDIO_DEVICE_BLOCK_SIZE-SDIO_BOOT_PKG_HEADER_SIZE;
                    start_address += SDIO_DEVICE_BLOCK_SIZE-SDIO_BOOT_PKG_HEADER_SIZE;
                    read_offset += SDIO_DEVICE_BLOCK_SIZE-SDIO_BOOT_PKG_HEADER_SIZE;

                    *p_len = residue-(SDIO_DEVICE_BLOCK_SIZE-SDIO_BOOT_PKG_HEADER_SIZE);
                    *p_dest_addr = start_address;
                    memcpy(tx_buffer+SDIO_BOOT_PKG_HEADER_SIZE, rx_buffer+read_offset, residue-(SDIO_DEVICE_BLOCK_SIZE-SDIO_BOOT_PKG_HEADER_SIZE));
                    // mutex_lock(&artosyn_mutex);
                    // memcpy(dev->tx_buf, tx_buffer, residue-(SDIO_DEVICE_BLOCK_SIZE-SDIO_BOOT_PKG_HEADER_SIZE));
                    // mutex_unlock(&artosyn_mutex);
                    sent = artosyn_sdio_write(dev, tx_buffer, residue-(SDIO_DEVICE_BLOCK_SIZE-SDIO_BOOT_PKG_HEADER_SIZE), false);
                    if(sent != residue-(SDIO_DEVICE_BLOCK_SIZE-SDIO_BOOT_PKG_HEADER_SIZE))
                    {
                        AR_DBG(DM_SDIO, DL_ERR, "uploading residue 2 error, read_length %d, send_length = %d, ret = %d, sent = %d, residue = %d\n", read_length, send_length, ret, sent, residue);
                        break;
                    }
                    read_length += residue-(SDIO_DEVICE_BLOCK_SIZE-SDIO_BOOT_PKG_HEADER_SIZE);
                    start_address += residue-(SDIO_DEVICE_BLOCK_SIZE-SDIO_BOOT_PKG_HEADER_SIZE);
                    read_offset += residue-(SDIO_DEVICE_BLOCK_SIZE-SDIO_BOOT_PKG_HEADER_SIZE);

                }
                else if(residue > 0)
                {
                    *p_len = residue;
                    *p_dest_addr = start_address;
                    memcpy(tx_buffer+SDIO_BOOT_PKG_HEADER_SIZE, rx_buffer+read_offset, residue);
                    // mutex_lock(&artosyn_mutex);
                    // memcpy(dev->tx_buf, tx_buffer, residue+SDIO_BOOT_PKG_HEADER_SIZE);
                    // mutex_unlock(&artosyn_mutex);
                    sent = artosyn_sdio_write(dev, tx_buffer, residue+SDIO_BOOT_PKG_HEADER_SIZE, false);
                    if(sent != residue+SDIO_BOOT_PKG_HEADER_SIZE)
                    {
                        AR_DBG(DM_SDIO, DL_ERR, "uploading residue 3 error, read_length %d, send_length = %d, ret = %d, sent = %d, residue = %d\n", read_length, send_length, ret, sent, residue);
                        break;
                    }
                    read_length += residue;
                    start_address += residue;
                    read_offset += residue;
                }
            }
        }
        else
        {
            AR_DBG(DM_SDIO, DL_ERR, "uploading read file error read_length %d \n", read_length);
            break;
        }
    }

    if (offset) {
        *offset = buf_offset;
    }

    kfree(tx_buffer);
    kfree(rx_buffer);

    return read_length;
}

/**
 * @brief Write firmware download req. Rgegister as API ar_sdio_send_dnld_req.
 *
 *  @param handle  Refers to oal_handle.
 *  @param addr    Address to upload the firmware data
 *  @param data    Firmware data to sent
 *  @param len     Firmware data len to sent
 *  @param timeout Timeout in unit ms
 *
 *  @return        OAL_STATUS_SUCCESS for success, otherwise fail.
 */
static oal_status
ar_sdio_send_dnld_req(IN oal_handle *handle, u32 addr, u8* data, u32 len, u16 timeout)
{
    oal_status status = OAL_STATUS_SUCCESS;
    struct artosyn_sdio_device *dev = NULL;
    uint32_t length = 0;

    if (!handle) {
        AR_DBG(DM_SDIO, DL_WRN, "%s: handle is NULL!\n", __func__);
        return OAL_STATUS_FAILURE;
    }

    dev = (struct artosyn_sdio_device *)handle->card;
    if (dev == NULL) {
        AR_DBG(DM_SDIO, DL_WRN, "%s: dev is NULL!\n", __func__);
        return OAL_STATUS_FAILURE;
    }

    length = sdio_rom_send(dev, data, 0, addr, len);
    if (length != len) {
        status = OAL_STATUS_FAILURE;
    }

    return status;
}

/**
 * @brief Write firmware download done. Rgegister as API ar_sdio_send_dnld_done.
 *
 *  @param handle  Refers to oal_handle.
 *  @param timeout Timeout in unit ms
 *
 *  @return        OAL_STATUS_SUCCESS for success, otherwise fail.
 */
static oal_status
ar_sdio_send_dnld_done(IN oal_handle *handle, u16 timeout)
{
    oal_status status = OAL_STATUS_SUCCESS;
    struct artosyn_sdio_device *dev = NULL;

    dev = (struct artosyn_sdio_device *)handle->card;
    if (dev == NULL) {
        AR_DBG(DM_SDIO, DL_WRN, "%s: dev is NULL!\n", __func__);
        return OAL_STATUS_FAILURE;
    }

    sdio_rom_send(dev, NULL, 0, 0, 0);

    return status;
}


/**
 * @brief Process the skb size and send to ar rpc module.
 *
 *  @param poal_handle  Refers to oal_handle.
 *  @param req          skbreq that received.
 *  @param status       Refers to oal_status.
 *
 *  @return             OAL_STATUS_SUCCESS for success, otherwise fail.
 */
static oal_status
ar_sdio_recv_packet_complete(IN oal_handle *poal_handle,
                                      IN struct ar_skbreq *req,
                                      IN oal_status status)
{
    struct sk_buff *rx_skb;
    oal_status ret = OAL_STATUS_SUCCESS;

    if (!poal_handle) {
        return OAL_STATUS_FAILURE;
    }

    if (!req) {
        ret = OAL_STATUS_FAILURE;
        goto fail;
    }

    if (ret == OAL_STATUS_SUCCESS) {
        if (poal_handle->state == AR_BUS_STATE_SLEEP) {
            AR_DBG(DM_SDIO, DL_WRN, "%s: dev state=%d, msg len=%d\n", __func__, poal_handle->state, req->actual_length);
        } else {
            AR_DBG(DM_SDIO, DL_VRB, "%s: dev state=%d, msg len=%d\n", __func__, poal_handle->state, req->actual_length);
        }

        if (req->skb) {
            if (sdio_rx_tp_test) {
                /* Skip the analyse if in tp test*/
                dev_kfree_skb_any(req->skb);
                req->skb = NULL;
            }
            else if (poal_handle) {
                ar_rpc_rx_pkt(poal_handle, req->skb);
            }
            else {
                /* Free the skb */
                dev_kfree_skb_any(req->skb);
                req->skb = NULL;
            }
        }

        /** Leave skb for rpc/net_dev */
        req->skb = NULL;
        oal_enqueue_skbreq_list(poal_handle, req, SKBREQ_LIST_PKTIN_FREE);
    } else {
        /* Free the skb */
        if (req->skb) {
            dev_kfree_skb_any(req->skb);
            req->skb = NULL;
        }

        oal_enqueue_skbreq_list(poal_handle, req, SKBREQ_LIST_PKTIN_FREE);
    }
    return OAL_STATUS_SUCCESS;

fail:
    return ret;
}

static int artosyn_sdio_read_condition(struct artosyn_sdio_device *dev)
{
    int ret = 0;
    mutex_lock(&artosyn_mutex);
    if (removed) {
        ret = 1;
    }
    else {
        sdio_claim_host(dev->func);
        if (dev->read_valid_size > dev->read_offset) {
            ret = 1;
        }
        sdio_release_host(dev->func);
    }
    mutex_unlock(&artosyn_mutex);

    return ret;
}

static int artosyn_sdio_write_condition(struct artosyn_sdio_device *dev)
{
    int ret = 0;
    mutex_lock(&artosyn_mutex);
    if (removed) {
        ret = 1;
    }
    else {
        sdio_claim_host(dev->func);
        if (dev->write_valid_size > dev->write_offset) {
            ret = 1;
        }
        sdio_release_host(dev->func);
    }
    mutex_unlock(&artosyn_mutex);

    return ret;
}

static ssize_t artosyn_sdio_write(struct artosyn_sdio_device *dev, uint8_t* buf, size_t count, bool non_block)
{
    int err = -1;
    int retry_count = 5;
    size_t count_bak = 0;
    unsigned int valid_size_bak = 0;
    ssize_t ret = 0;

    if (dev->rom_mode) {
        dev->write_offset = 0;
        dev->write_valid_size = count;
    }

    mutex_lock(&artosyn_mutex);
    if (removed) {
        mutex_unlock(&artosyn_mutex);
        AR_DBG(DM_SDIO, DL_INF, "%s: device removed\n", __func__);
        return -EIO;
    }
    if (dev->writing) {
        mutex_unlock(&artosyn_mutex);
        AR_DBG(DM_SDIO, DL_ERR, "%s: busy\n", __func__);
        return -EBUSY;
    }

    dev->writing = 1;
    mutex_unlock(&artosyn_mutex);

    sdio_claim_host(dev->func);
    if ((dev->write_valid_size == dev->write_offset) && non_block) {
        dev->writing = 0;
        sdio_release_host(dev->func);
        return -EAGAIN;
    }
    sdio_release_host(dev->func);

    if (!dev->rom_mode) {
        AR_DBG(DM_SDIO, DL_INF, "%s: write wait\n", __func__);

        do {
            err = wait_event_interruptible_timeout(dev->tx_q, artosyn_sdio_write_condition(dev), 10);
            if (err > 0) {
                break;
            }

#ifdef WORKAROUND_FOR_INTERRUPT_LOST_ISSUE
            // check reg
            if (dev->handle) {
                oal_queue_work(dev->handle, dev->reg_workqueue);
            }
#endif
            retry_count--;
        } while((err <= 0) && (retry_count > 0));

        if (retry_count == 0)
        {
            AR_DBG(DM_SDIO, DL_ERR, "%s: write wait timeout\n", __func__);
            dev->writing = 0;
            return -EIO;
        }

        AR_DBG(DM_SDIO, DL_INF, "%s: write wait done\n", __func__);
    }
    else {
        udelay(tx_sleep_us);//delay
    }

    mutex_lock(&artosyn_mutex);
    if (removed) {
        dev->writing = 0;
        mutex_unlock(&artosyn_mutex);
        AR_DBG(DM_SDIO, DL_INF, "%s: device removed\n", __func__);
        return -EIO;
    }

    sdio_claim_host(dev->func);
    valid_size_bak = dev->write_valid_size;
    if (dev->write_valid_size == dev->write_offset)
    {
        dev->writing = 0;
        sdio_release_host(dev->func);
        AR_DBG(DM_SDIO, DL_ERR, "%s: dev->write_valid_size == dev->write_offset %d return\n", __func__, dev->write_valid_size);
        mutex_unlock(&artosyn_mutex);
        return -EFAULT;
    }

    if (count > (dev->write_valid_size - dev->write_offset)) {
        AR_DBG(DM_SDIO, DL_WRN, "%s: write count %zu > (dev->write_valid_size(%d) - dev->write_offset) %d\n", __func__,
                count, dev->write_valid_size, (dev->write_valid_size - dev->write_offset));
        count = dev->write_valid_size - dev->write_offset;
    }
    count_bak = count;
    if (count > SDIO_TRANS_MAX_SZIE) {
        AR_DBG(DM_SDIO, DL_WRN, "%s: write count %zu > max size %d\n", __func__,
                count, SDIO_TRANS_MAX_SZIE);
        count = SDIO_TRANS_MAX_SZIE;
    }

    if (count > dev->func->cur_blksize) {
        if (count % dev->func->cur_blksize) {
            AR_DBG(DM_SDIO, DL_ERR, "%s: write count %zu > dev->func->cur_blksize %d\n", __func__,
                    count, dev->func->cur_blksize);
        }
        count -= (count % dev->func->cur_blksize);
    }
    if (valid_size_bak != dev->write_valid_size) {
        AR_DBG(DM_SDIO, DL_WRN, "artosyn sdio write count = %zu, blksize %d, validsize %d, offset %d, size %d, %d %zu\n",
                count, dev->func->cur_blksize, dev->write_valid_size, dev->write_offset,
                dev->write_valid_size-dev->write_offset, valid_size_bak, count_bak);
    }

    // print_buf("sdio tx hw:", dev->tx_buf, count);

    ret = sdio_memcpy_toio(dev->func, FIFO_ADDRESS, buf, count);
    if (ret)
        AR_DBG(DM_SDIO, DL_ERR, "artosyn sdio write failed, ret %zu \n", ret);
    else {
        /* write transfer is over */
        dev->write_offset = dev->write_valid_size;
        dev->write_size = dev->write_valid_size;

        total_tx_pkts++;
        total_tx_bytes += count;

        sdio_tx_pkts_period++;
        sdio_tx_bytes_period += count;
    }

    dev->writing = 0;
    sdio_release_host(dev->func);
    mutex_unlock(&artosyn_mutex);

    return ret==0 ? count : ret;
}

static ssize_t artosyn_read(struct artosyn_sdio_device *dev, uint8_t *buf, size_t count)
{
    int err = -1;
    ssize_t ret = 0;
    int retry_count = 10;

    if (!buf || (count == 0)) {
        AR_DBG(DM_SDIO, DL_ERR, "%s: buf %hhn count %zu\n", __func__, buf, count);
        return 0;
    }

    mutex_lock(&artosyn_mutex);
    if (removed) {
        AR_DBG(DM_SDIO, DL_INF, "%s: device removed\n", __func__);
        mutex_unlock(&artosyn_mutex);
        return -EIO;
    }
    if (dev->reading) {
        AR_DBG(DM_SDIO, DL_ERR, "%s: busy\n", __func__);
        mutex_unlock(&artosyn_mutex);
        return -EBUSY;
    }
    dev->reading = 1;
    mutex_unlock(&artosyn_mutex);

// #if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 0, 0)
//     if (unlikely(!access_ok(buf, count)))
// #else
//     if (unlikely(!access_ok(VERIFY_WRITE, buf, count)))
// #endif
//     {
//         AR_DBG(DM_SDIO, DL_ERR, "%s: access failed!\n", __func__);
//         dev->reading = 0;
//         return -EFAULT;
//     }

    sdio_claim_host(dev->func);
    if (dev->read_valid_size == dev->read_offset) {
        AR_DBG(DM_SDIO, DL_ERR, "%s: dev->read_valid_size == dev->read_offset %d\n",
                    __func__, dev->read_valid_size);
        dev->reading = 0;
        sdio_release_host(dev->func);
        return -EAGAIN;
    }
    sdio_release_host(dev->func);

    if (!dev->rom_mode) {
        AR_DBG(DM_SDIO, DL_INF, "%s: read wait\n", __func__);

        do {
            // err = wait_event_interruptible_timeout(dev->rx_q, artosyn_sdio_read_condition(dev), 10);
            // if (err > 0) {
            //     break;
            // }
            if (artosyn_sdio_read_condition(dev)) {
                break;
            }

            msleep(10);

#ifdef WORKAROUND_FOR_INTERRUPT_LOST_ISSUE
            //check reg
            if (dev->handle) {
                oal_queue_work(dev->handle, dev->reg_workqueue);
            }
#endif
            retry_count--;
        } while((!artosyn_sdio_read_condition(dev)) && (retry_count > 0));

        if (retry_count == 0)
        {
            AR_DBG(DM_SDIO, DL_ERR, "%s: read wait timeout\n", __func__);
            dev->reading = 0;
            return -EIO;
        }

        // if (!artosyn_sdio_read_condition(dev)) {
        //     AR_DBG(DM_SDIO, DL_WRN, "%s: read wait timeout\n", __func__);
        //     dev->reading = 0;
        //     return err;
        // }
        AR_DBG(DM_SDIO, DL_INF, "%s: read wait done\n", __func__);
    }
    else {
        udelay(rx_sleep_us);//delay
    }

    mutex_lock(&artosyn_mutex);
    if (removed) {
        dev->reading = 0;
        mutex_unlock(&artosyn_mutex);
        AR_DBG(DM_SDIO, DL_INF, "%s: device removed\n", __func__);
        return -EIO;
    }
    sdio_claim_host(dev->func);
    if (count > SDIO_TRANS_MAX_SZIE) {
        AR_DBG(DM_SDIO, DL_INF, "%s: read count %zu > SDIO_TRANS_MAX_SZIE %d\n", __func__,count, SDIO_TRANS_MAX_SZIE);
        count = SDIO_TRANS_MAX_SZIE;
    }
    if (count > (dev->read_valid_size - dev->read_offset)) {
        AR_DBG(DM_SDIO, DL_INF, "%s: read count %zu > (dev->read_valid_size - dev->read_offset) %d\n", __func__,count, (dev->read_valid_size - dev->read_offset));
        count = dev->read_valid_size - dev->read_offset;
    }
    if (count == 0) {
        AR_DBG(DM_SDIO, DL_ERR, "%s: read count is zero!\n", __func__);
        dev->reading = 0;
        sdio_release_host(dev->func);
        mutex_unlock(&artosyn_mutex);
        return -EFAULT;
    }

    ret = sdio_memcpy_fromio(dev->func, buf, FIFO_ADDRESS, count);
    if (ret) {
        AR_DBG(DM_SDIO, DL_ERR, "artosyn sdio read failed, ret %zu \n", ret);
        goto out;
    }
    else
    {
        dev->read_offset += count;
        dev->read_size = dev->read_valid_size;

        total_rx_pkts++;
        total_rx_bytes += count;

        sdio_rx_pkts_period++;
        sdio_rx_bytes_period += count;
    }
out:
    sdio_release_host(dev->func);
    dev->reading = 0;
    mutex_unlock(&artosyn_mutex);
    return ret==0 ? count : ret;
}

/**
 * @brief Write data as sync mode and call complete func after data sent.
 *        Rgegister as API ar_usb_write_data_sync.
 *
 *  @param handle  Refers to oal_handle.
 *  @param req     skreq in list includes skb and urb.
 *
 *  @return        OAL_STATUS_SUCCESS for success, otherwise fail.
 */
static oal_status
ar_sdio_write_data_sync(oal_handle *handle, IN struct ar_skbreq *req)
{
    int write_ret = 0;
    oal_status ret = OAL_STATUS_SUCCESS;
    struct artosyn_sdio_device *dev = NULL;
    struct sk_buff *tx_skb = NULL;
    gfp_t flag;

    AR_DBG(DM_SDIO, DL_VRB, AR_FN_ENTRY_STR);

    if (handle == NULL) {
        AR_DBG(DM_SDIO, DL_ERR, "%s: handle is NULL!\n", __func__);
        ret = OAL_STATUS_FAILURE;
        goto exit;
    }

    dev = (struct artosyn_sdio_device *)handle->card;
    if (dev == NULL) {
        AR_DBG(DM_SDIO, DL_ERR, "%s: dev is NULL!\n", __func__);
        ret = OAL_STATUS_FAILURE;
        goto free_req;
    }

    if (req->skb == NULL) {
        AR_DBG(DM_SDIO, DL_ERR, "%s: skb is NULL!\n", __func__);
        ret = OAL_STATUS_FAILURE;
        goto free_req;
    }

    if ((req->skb->len > dev->func->cur_blksize) && (req->skb->len % dev->func->cur_blksize != 0)) {
        flag = (in_atomic() || irqs_disabled())? GFP_ATOMIC : GFP_KERNEL;
        tx_skb = skb_copy_expand(req->skb, skb_headroom(req->skb),
                skb_tailroom(req->skb) + dev->func->cur_blksize - (req->skb->len % dev->func->cur_blksize), flag);
        if (!tx_skb) {
            AR_DBG(DM_SDIO, DL_ERR, "%s: skb expend failed\n", __func__);
            ret = OAL_STATUS_FAILURE;
            goto exit;
        }
        else {
            skb_put(tx_skb, dev->func->cur_blksize - (req->skb->len % dev->func->cur_blksize));
            memset(tx_skb->data + req->skb->len, 0, dev->func->cur_blksize - (req->skb->len % dev->func->cur_blksize));
            dev_kfree_skb_any(req->skb);
            req->skb = tx_skb;
        }
    }

    write_ret = artosyn_sdio_write(dev, req->skb->data, req->skb->len, false);
    if (write_ret < 0) {
        AR_DBG(DM_SDIO, DL_INF, "%s: artosyn_sdio_write failed err %d req->type %d\n", __func__, write_ret, req->type);
        ret = OAL_STATUS_FAILURE;
    }

free_req:
    if (is_message_type(req->type)) {
        oal_send_msg_complete(handle, req, ret);
    }

    if (is_packet_type(req->type)) {
        oal_send_packet_complete(handle, req, ret);
    }

exit:
    AR_DBG(DM_SDIO, DL_VRB, AR_FN_LEAVE_STR);
    return ret;
}


/**
 * @brief Read data as sync mode and call complete func after data received.
 *        Rgegister as API oal_read_data_sync.
 *
 *  @param handle  Refers to oal_handle.
 *  @param req     skreq in list includes skb and urb.
 *
 *  @return        OAL_STATUS_SUCCESS for success, otherwise fail.
 */
static oal_status
ar_sdio_read_data_sync(oal_handle *handle, struct ar_skbreq *req)
{
    ssize_t read_size = 0;
    oal_status ret = OAL_STATUS_SUCCESS;
    struct artosyn_sdio_device *dev = NULL;

    if (handle == NULL) {
        AR_DBG(DM_SDIO, DL_ERR, "%s: handle is NULL!\n", __func__);
        ret = OAL_STATUS_FAILURE;
        goto exit;
    }

    dev = (struct artosyn_sdio_device *)handle->card;
    if (dev == NULL) {
        AR_DBG(DM_SDIO, DL_ERR, "%s: dev is NULL!\n", __func__);
        ret = OAL_STATUS_FAILURE;
        goto exit;
    }

    /** no mem,read and drop data */
    if (!req || !req->skb) {
        artosyn_read(dev, dev->rx_buf, SDIO_TRANS_MAX_SZIE);
        AR_DBG(DM_SDIO, DL_WRN, "%s: lack of mem, drop\n", __func__);
        ret = OAL_STATUS_FAILURE;
        goto exit;
    }
    else {
        skb_trim(req->skb, 0);

        read_size = artosyn_read(dev, req->skb->data, skb_tailroom(req->skb));
        if (read_size <= 0) {
            AR_DBG(DM_SDIO, DL_WRN, "%s: read FAILED read_size %zu\n", __func__, read_size);
            ret = OAL_STATUS_FAILURE;
            goto exit;
        } else {
            req->actual_length = read_size;
            skb_put(req->skb, req->actual_length);
        }

        /** Push skbreq to post list and waiting for pop by rx workqueue */
        oal_enqueue_skbreq_list(handle, req, SKBREQ_LIST_PKTIN_POST);

        AR_DBG(DM_GENERIC, DL_INF, "%s: trigger main_workqueue %p\n", __func__, handle->main_workqueue);

        /* trigger main workqueue */
        oal_queue_work(handle, handle->main_workqueue);
    }

exit:
    return ret;
}

/**
 * @brief Update the type of the handle. Rgegister as API oal_update_card_type.
 *
 *  @param handle  Refers to oal_handle.
 *  @param card    Diffferent kind of card priv structure.
 *
 *  @return        N/A.
 */
static void
ar_sdio_update_card_type(oal_handle *handle, void *card)
{
    struct artosyn_sdio_device *dev = (struct artosyn_sdio_device *)card;
    if (dev) {
        AR_DBG(DM_SDIO, DL_ERR, "set dev oal handle %p\n", handle);
        dev->handle = handle;
    }
}


static void artosyn_sdio_reg_check(struct sdio_func *func)
{
    int i = 0;
    int ret = -1;
    unsigned int size = 0;
    unsigned int read_int_handled = 0;
    unsigned int write_int_handled = 0;
    oal_handle *handle = NULL;
    struct artosyn_sdio_device *dev = sdio_get_drvdata(func);

    if (!dev) {
        AR_DBG(DM_SDIO, DL_ERR, "dev is NULL!\n");
        return;
    }

    handle = dev->handle;

#ifdef WORKAROUND_FOR_INTERRUPT_LOST_ISSUE
    while(1)
#endif
    {
        u8 iir = 0;
        sdio_claim_host(func);

        iir = sdio_readb(func, 0x13, &ret);
        AR_DBG(DM_SDIO, DL_INF, "artosyn_sdio_reg_check iir = %x, ret = %d\n", iir, ret);
#ifdef WORKAROUND_FOR_INTERRUPT_LOST_ISSUE
        if(ret != 0 || iir == 0)
        {
            sdio_release_host(func);
            break;
        }
#endif
        if (iir & 1) {
#if 0
            size = (sdio_readb(func, 0x2e, NULL) << 16) | (sdio_readb(func, 0x2d, NULL) << 8) | sdio_readb(func, 0x2c, NULL);
            if (read_int_handled || dev->read_offset != dev->read_valid_size) {
                AR_DBG(DM_SDIO, DL_MSG, "artosyn_sdio_irqhandler %d-%d-%d-%d (%d %d) bytes for read lost\n", dev->read_valid_size, dev->read_offset, size, dev->read_size, read_int_handled, write_int_handled);
            }
            else {
                dev->read_offset = 0;
                dev->read_valid_size = size;
                read_int_handled = 1;
                //AR_DBG(DM_SDIO, DL_MSG, "artosyn_sdio_reg_check %d bytes ready for read\n", dev->read_valid_size);
                wake_up_interruptible_all(&dev->rx_q);
            }
#endif
        }
        if (iir & (1<<4)) {
            u8 mb_ena = sdio_readb(func, 0x68, NULL);
            AR_DBG(DM_SDIO, DL_INF, "artosyn_sdio_reg_check mail box interrupt %x\n", mb_ena);
            for (i=0;i<SDIO_MAILBOX_CHANNEL_COUNT;i++) {
                if (mb_ena & (1<<i)) {
                    u8 mail = sdio_readb(func, 0x54+i*4, NULL);
                    AR_DBG(DM_SDIO, DL_INF, "artosyn_sdio_reg_check channel %d mail %x\n", i, mail);
                    dev->msg_valid[i] = 1;
                    dev->message[i] = mail;
                    wake_up_interruptible_all(&dev->mailbox_q);
                }
            }

            /* rx ready */
            if (mb_ena & (1<<2)) {
#if 1
                size = (sdio_readb(func, 0x5c, NULL) << 16) | (sdio_readb(func, 0x7c, NULL) << 8) | sdio_readb(func, 0x80, NULL);
                if (read_int_handled || dev->read_offset != dev->read_valid_size) {
                    AR_DBG(DM_SDIO, DL_INF, "artosyn_sdio_reg_check %d-%d-%d-%d (%d %d) bytes for read lost\n", dev->read_valid_size, dev->read_offset, size, dev->read_size, read_int_handled, write_int_handled);
                }
                else {
                    dev->read_offset = 0;
                    dev->read_valid_size = size;
                    read_int_handled = 1;
                    AR_DBG(DM_SDIO, DL_INF, "artosyn_sdio_reg_check %d bytes ready for read\n", dev->read_valid_size);
                    if (dev->handle) {
                        oal_queue_work(dev->handle, dev->rx_workqueue);
                    }
                    // wake_up_interruptible_all(&dev->rx_q);
                }
#endif
            }

            /* tx ready */
            if (mb_ena & (1<<3)) {
                size = (sdio_readb(func, 0x60, NULL) << 16) | (sdio_readb(func, 0x84, NULL) << 8) | sdio_readb(func, 0x88, NULL);
                if (write_int_handled || dev->write_offset != dev->write_valid_size) {
                    AR_DBG(DM_SDIO, DL_INF, "%d-%d-%d-%d (%d %d) duplicate write interrupt\n", dev->write_valid_size, dev->write_offset, size, dev->write_size, read_int_handled, write_int_handled);
                }
                else {
                    AR_DBG(DM_SDIO, DL_INF, "%d-%d-%d-%d (%d %d) write interrupt\n", dev->write_valid_size, dev->write_offset, size, dev->write_size, read_int_handled, write_int_handled);
                    dev->write_offset = 0;
                    dev->write_valid_size = size;
                    write_int_handled = 1;
                    wake_up_interruptible_all(&dev->tx_q);
                }
            }
        }
        sdio_release_host(func);
    }
}

static void artosyn_sdio_irqhandler(struct sdio_func *func)
{
    int i = 0;
    int ret = -1;
    unsigned int size = 0;
    unsigned int read_int_handled = 0;
    unsigned int write_int_handled = 0;
    oal_handle *handle = NULL;
    struct artosyn_sdio_device *dev = sdio_get_drvdata(func);

    if (!dev) {
        AR_DBG(DM_SDIO, DL_ERR, "dev is NULL!\n");
        return;
    }

    handle = dev->handle;

#ifdef WORKAROUND_FOR_INTERRUPT_LOST_ISSUE
    while(1)
#endif
    {
        u8 iir = 0;

        iir = sdio_readb(func, 0x13, &ret);
        AR_DBG(DM_SDIO, DL_INF, "artosyn_sdio_irqhandler iir = %x, ret = %d\n", iir, ret);
#ifdef WORKAROUND_FOR_INTERRUPT_LOST_ISSUE
        if(ret != 0 || iir == 0)
        {
            break;
        }
#endif
        if (iir & 1) {
#if 0
            size = (sdio_readb(func, 0x2e, NULL) << 16) | (sdio_readb(func, 0x2d, NULL) << 8) | sdio_readb(func, 0x2c, NULL);
            if (read_int_handled || dev->read_offset != dev->read_valid_size) {
                AR_DBG(DM_SDIO, DL_MSG, "artosyn_sdio_irqhandler %d-%d-%d-%d (%d %d) bytes for read lost\n", dev->read_valid_size, dev->read_offset, size, dev->read_size, read_int_handled, write_int_handled);
            }
            else {
                dev->read_offset = 0;
                dev->read_valid_size = size;
                read_int_handled = 1;
                //AR_DBG(DM_SDIO, DL_MSG, "artosyn_sdio_irqhandler %d bytes ready for read\n", dev->read_valid_size);
                wake_up_interruptible_all(&dev->rx_q);
            }
#endif
        }
        if (iir & (1<<4)) {
            u8 mb_ena = sdio_readb(func, 0x68, NULL);
            AR_DBG(DM_SDIO, DL_INF, "artosyn_sdio_irqhandler mail box interrupt %x\n", mb_ena);
            for (i=0;i<SDIO_MAILBOX_CHANNEL_COUNT;i++) {
                if (mb_ena & (1<<i)) {
                    u8 mail = sdio_readb(func, 0x54+i*4, NULL);
                    AR_DBG(DM_SDIO, DL_INF, "artosyn_sdio_irqhandler channel %d mail %x\n", i, mail);
                    dev->msg_valid[i] = 1;
                    dev->message[i] = mail;
                    wake_up_interruptible_all(&dev->mailbox_q);
                }
            }

            /* rx ready */
            if (mb_ena & (1<<2)) {
#if 1
                total_rx_isr++;
                size = (sdio_readb(func, 0x5c, NULL) << 16) | (sdio_readb(func, 0x7c, NULL) << 8) | sdio_readb(func, 0x80, NULL);
                if (read_int_handled || dev->read_offset != dev->read_valid_size) {
                    AR_DBG(DM_SDIO, DL_WRN, "artosyn_sdio_irqhandler %d-%d-%d-%d (%d %d) bytes for read lost\n", dev->read_valid_size, dev->read_offset, size, dev->read_size, read_int_handled, write_int_handled);
                }
                else {
                    dev->read_offset = 0;
                    dev->read_valid_size = size;
                    read_int_handled = 1;
                    AR_DBG(DM_SDIO, DL_INF, "artosyn_sdio_irqhandler %d bytes ready for read\n", dev->read_valid_size);
                    if (dev->handle) {
                        oal_queue_work(dev->handle, dev->rx_workqueue);
                    }
                    // wake_up_interruptible_all(&dev->rx_q);
                }
#endif
            }

            /* tx ready */
            if (mb_ena & (1<<3)) {
                total_tx_isr++;
                size = (sdio_readb(func, 0x60, NULL) << 16) | (sdio_readb(func, 0x84, NULL) << 8) | sdio_readb(func, 0x88, NULL);
                if (write_int_handled || dev->write_offset != dev->write_valid_size) {
                    AR_DBG(DM_SDIO, DL_WRN, "%d-%d-%d-%d (%d %d) duplicate write interrupt\n", dev->write_valid_size, dev->write_offset, size, dev->write_size, read_int_handled, write_int_handled);
                }
                else {
                    AR_DBG(DM_SDIO, DL_INF, "%d-%d-%d-%d (%d %d) write interrupt\n", dev->write_valid_size, dev->write_offset, size, dev->write_size, read_int_handled, write_int_handled);
                    dev->write_offset = 0;
                    dev->write_valid_size = size;
                    write_int_handled = 1;
                    wake_up_interruptible_all(&dev->tx_q);
                }
            }
        }
    }
}

/**
 *  @brief This workqueue function check sdio reg
 *
 *  @param handle    A pointer to oal_handle
 *  @param priv      Private data
 *
 *  @return        N/A
 */
static void
ar_sdio_reg_check(IN oal_handle *handle, IN void *priv)
{
    struct artosyn_sdio_device *dev = NULL;

    if (!handle) {
        AR_DBG(DM_SDIO, DL_WRN, "%s: handle is NULL!\n", __func__);
        return;
    }

    dev = (struct artosyn_sdio_device *)handle->card;
    if (dev == NULL) {
        AR_DBG(DM_SDIO, DL_WRN, "%s: dev is NULL!\n", __func__);
        return;
    }

    mutex_lock(&artosyn_mutex);
    artosyn_sdio_reg_check(dev->func);
    mutex_unlock(&artosyn_mutex);
}

/**
 *  @brief This workqueue function for rx process
 *
 *  @param handle    A pointer to oal_handle
 *  @param priv      Private data
 *
 *  @return        N/A
 */
static void
ar_sdio_rx_func(IN oal_handle *handle, IN void* priv)
{
    struct ar_skbreq *req = NULL;
    struct artosyn_sdio_device *dev = NULL;
    oal_status status = OAL_STATUS_SUCCESS;

    total_rx_process++;

    if (!handle) {
        AR_DBG(DM_SDIO, DL_WRN, "%s: handle is NULL!\n", __func__);
        return;
    }

    dev = (struct artosyn_sdio_device *)handle->card;
    if (dev == NULL) {
        AR_DBG(DM_SDIO, DL_WRN, "%s: dev is NULL!\n", __func__);
        return;
    }

    if (handle && handle->callbacks.oal_read_data_sync) {
        req = oal_dequeue_skbreq_list(handle, SKBREQ_LIST_PKTIN_FREE);
        if (req) {
            req->skb = dev_alloc_skb(handle->pktin_mtu+1);
            if (!req->skb) {
                AR_DBG(DM_SDIO, DL_WRN, "ar_sdio_rx_func dev_alloc_skb %d failed\n", handle->pktin_mtu+1);
            }
        }
        else {
            AR_DBG(DM_SDIO, DL_WRN, "ar_sdio_rx_func dequeue failed\n");
        }

        status = handle->callbacks.oal_read_data_sync(handle, req);
        if (req && (status != OAL_STATUS_SUCCESS)) {
            /** Push skb back to free queue */
            oal_enqueue_skbreq_list(handle, req, SKBREQ_LIST_PKTIN_FREE);
        }
    }
}

int sdio_tp_test_stats_thread_func(void *data)
{
    ktime_t last, cur;
    uint64_t time;
    uint64_t rate = 0;
    oal_handle *handle = (oal_handle *) data;

    AR_DBG(DM_GENERIC, DL_INF, AR_FN_ENTRY_STR);

    /** Reset the stats */
    total_rx_pkts = 0;
    total_rx_bytes = 0;
    sdio_rx_pkts_period = 0;
    sdio_rx_bytes_period = 0;

    total_tx_pkts = 0;
    total_tx_bytes = 0;
    sdio_tx_pkts_period = 0;
    sdio_tx_bytes_period = 0;

    last = ktime_get();

    while (!kthread_should_stop()) {
        msleep(1000);

        if (!handle || (handle->state != AR_BUS_STATE_UP)) {
            AR_DBG(DM_GENERIC, DL_ERR,"handle is not inited!\n handle %p state:%d\n", handle, handle?handle->state:-1);
            continue;
        }

        if (removed) {
            continue;
        }

        cur = ktime_get();

        time = ((uint64_t)ktime_to_ns(ktime_sub(cur, last)))/1000/1000;  //ms

        //rx
        if (sdio_rx_tp_test) {
            rate = sdio_rx_bytes_period * 8 * 1000 / time;

            AR_DBG(DM_SDIO, DL_MSG, "=====sdio rx stats=====\n");
            AR_DBG(DM_SDIO, DL_MSG, "[total stats]--- pkts: %llu, bytes %llu\n", total_rx_pkts, total_rx_bytes);
            AR_DBG(DM_SDIO, DL_MSG, "[rate stats] --- pkts_rate: %llu pps, bytes_rate %llu.%llu Mbps\n",
                        sdio_rx_pkts_period * 1000 / time, rate / 1024 / 1024, ((rate / 1024 * 1000 / 1024) % 1000));

            sdio_rx_pkts_period = 0;
            sdio_rx_bytes_period = 0;
        }

        //tx
        if (sdio_tx_tp_test) {
            rate = sdio_tx_bytes_period * 8 * 1000 / time;

            AR_DBG(DM_SDIO, DL_MSG, "=====sdio tx stats=====\n");
            AR_DBG(DM_SDIO, DL_MSG, "[total stats]--- pkts: %llu, bytes %llu\n", total_tx_pkts, total_tx_bytes);
            AR_DBG(DM_SDIO, DL_MSG, "[rate stats] --- pkts_rate: %llu pps, bytes_rate %llu.%llu Mbps\n",
                        sdio_tx_pkts_period * 1000 / time, rate / 1024 / 1024, ((rate / 1024 * 1000 / 1024) % 1000));

            sdio_tx_pkts_period = 0;
            sdio_tx_bytes_period = 0;
        }

        last = cur;
    }

    AR_DBG(DM_GENERIC, DL_INF, AR_FN_LEAVE_STR);

    return 0;
}

int sdio_tx_tp_test_thread_func(void *data)
{
    int ret = 0;
    oal_status status;
    struct sk_buff *skb = NULL;
    oal_handle *handle = (oal_handle *) data;

    AR_DBG(DM_GENERIC, DL_INF, AR_FN_ENTRY_STR);

    if (!handle) {
        AR_DBG(DM_GENERIC, DL_INF, AR_FN_LEAVE_STR);
        return -1;
    }

    while (!kthread_should_stop()) {
        if (handle->state != AR_BUS_STATE_UP) {
            msleep(100);
            continue;
        }

        if (removed) {
            msleep(100);
            continue;
        }

        /** Skip data send if sdio_tx_tp_test is disabled */
        if (!sdio_tx_tp_test) {
            msleep(1000);
            continue;
        }

        skb = dev_alloc_skb(sdio_tx_tp_test_len);
        if (!skb) {
            AR_DBG(DM_GENERIC, DL_WRN, "%s: Error! allocate skb failed!\n", __func__);
            msleep(1);
            continue;
        }

        skb_put(skb, sdio_tx_tp_test_len);

        status = oal_send_packet_req(handle, skb, false);
        if (status != OAL_STATUS_SUCCESS) {
            /** Free the skb if enqueue skb failed.  */
            dev_kfree_skb_any(skb);
            msleep(1);
        }
    }

    AR_DBG(DM_GENERIC, DL_INF, AR_FN_LEAVE_STR);
    return ret;
}

static int sdio_artosyn_probe(struct sdio_func *func,
        const struct sdio_device_id *id)
{
    int ret = -1;
    oal_card_mode mode;
    oal_handle *handle = NULL;
    oal_workqueue *reg_work_queue = NULL;
    oal_workqueue *rx_work_queue = NULL;
    struct artosyn_sdio_device *dev = kzalloc(sizeof(struct artosyn_sdio_device), GFP_KERNEL);

    AR_DBG(DM_SDIO, DL_MSG, "artosyn sdio device probe\n");

    if (!dev) {
        AR_DBG(DM_SDIO, DL_ERR, "kzalloc priv data failed!\n");
        return -ENOMEM;
    }

    dev->func = func;
    sdio_set_drvdata(func, dev);

    init_waitqueue_head(&dev->mailbox_q);
    init_waitqueue_head(&dev->tx_q);
    init_waitqueue_head(&dev->rx_q);

    sdio_claim_host(func);

    sdio_auto_scan_enable(func, true);

    ret = sdio_enable_func(dev->func);
    if (ret) {
        sdio_release_host(func);
        AR_DBG(DM_SDIO, DL_ERR, "%s: sdio_enable_func() failed: ret=%d\n", __func__, ret);
        kfree(dev);
        return ret;
    }
    ret = sdio_claim_irq(func, artosyn_sdio_irqhandler);
    if (ret != 0) {
        sdio_release_host(func);
        AR_DBG(DM_SDIO, DL_ERR, "%s: sdio_claim_irq() failed: ret=%d\n", __func__, ret);
        kfree(dev);
        return ret;
    }

    /* host inerrupt enable signal */
    sdio_writeb(dev->func, 1, 0x14, NULL);
    /* host enable slave to master mb interrupt[1-4] */
    sdio_writeb(dev->func, 0xF0, 0x68, NULL);
    sdio_release_host(func);

    mutex_lock(&artosyn_mutex);
    removed = 0;
    mutex_unlock(&artosyn_mutex);

    /*5. Init oal */
    if ((func->vendor == ARTO_ROMCODE_VID) && (func->device == ARTO_ROMCODE_PID)) {
        dev->rom_mode = true;
        mode = OAL_CARD_MODE_FW_DNLD;
    }
    else if ((func->vendor == ARTO_RTOS_VID) && (func->device == ARTO_RTOS_PID)) {
        mode = OAL_CARD_MODE_RPC;
    }
    else {
        ret = -ENODEV;
        goto fail;
    }

    handle = oal_add_card((void*)dev, NULL, mode, &oal_cb);
    if (handle == NULL) {
        AR_DBG(DM_GENERIC, DL_ERR, "oal_add_card failed!\n");
        goto fail;
    }

    /* Create workqueue for isr reg check process */
    ret = oal_init_workqueue(handle, &reg_work_queue, "SDIO_REG_WORK_QUEUE", WQ_HIGHPRI | WQ_MEM_RECLAIM | WQ_UNBOUND, ar_sdio_reg_check, NULL);
    if (ret != OAL_STATUS_SUCCESS) {
        AR_DBG(DM_GENERIC, DL_ERR, "%s: sdio reg workqueue init failed\n", __func__);
        ret = OAL_STATUS_FAILURE;
        goto fail;
    }
    dev->reg_workqueue = (void*)reg_work_queue;

    /* Create workqueue for main process */
    ret = oal_init_workqueue(handle, &rx_work_queue, "SDIO_RX_WORK_QUEUE", WQ_HIGHPRI | WQ_MEM_RECLAIM | WQ_UNBOUND, ar_sdio_rx_func, NULL);
    if (ret != OAL_STATUS_SUCCESS) {
        AR_DBG(DM_GENERIC, DL_ERR, "%s: sdio rx workqueue init failed\n", __func__);
        ret = OAL_STATUS_FAILURE;
        goto fail;
    }
    dev->rx_workqueue = (void*)rx_work_queue;

    AR_DBG(DM_SDIO, DL_MSG, "artosyn sdio device added\n");

    total_tx_pkts = 0;
    total_tx_bytes = 0;
    total_rx_pkts = 0;
    total_rx_bytes = 0;

    total_rx_isr = 0;
    total_rx_process = 0;
    total_tx_isr = 0;

    oal_create_thread(sdio_tp_test_stats_thread_func, &sdio_tp_test_stats_thread, (void*)handle,
                "sdio_tp_test_stats_thread");

    oal_create_thread(sdio_tx_tp_test_thread_func, &sdio_tx_tp_test_thread, (void*)handle,
                "sdio_tx_tp_test_thread");

    return 0;

fail:
    sdio_claim_host(func);
    sdio_disable_func(func);
    sdio_release_irq(func);
    kfree(dev);
    sdio_release_host(func);

    return ret;
}

static void sdio_artosyn_remove(struct sdio_func *func)
{
    int ret = -1;
    struct artosyn_sdio_device *dev = sdio_get_drvdata(func);

    mutex_lock(&artosyn_mutex);
    removed = 1;
    mutex_unlock(&artosyn_mutex);
    while (dev->reading || dev->writing) {
        AR_DBG(DM_SDIO, DL_MSG, "artosyn sdio device remove reading %d writing %d\n",
                dev->reading, dev->writing);
        wake_up_interruptible_all(&dev->tx_q);
        msleep(1000);
    }

    oal_stop_thread(&sdio_tp_test_stats_thread);
    oal_stop_thread(&sdio_tx_tp_test_thread);

    sdio_claim_host(func);
    sdio_disable_func(func);
    sdio_release_irq(func);

    /* Terminate reg workqueue */
    if (dev->reg_workqueue) {
        oal_deinit_workqueue(dev->handle, dev->reg_workqueue);
        dev->reg_workqueue = NULL;
    }

    /* Terminate reg workqueue */
    if (dev->rx_workqueue) {
        oal_deinit_workqueue(dev->handle, dev->rx_workqueue);
        dev->rx_workqueue = NULL;
    }

    oal_remove_card(dev);

    kfree(dev);
    sdio_release_host(func);

    AR_DBG(DM_SDIO, DL_MSG, "artosyn sdio device removed\n");

    return;
}

int sdio_artosyn_init(void)
{
    int ret;

    ret = sdio_register_driver(&sdio_artosyn_driver);
    if (ret)
        goto err;

    return 0;

err:
    return ret;
}

void sdio_artosyn_exit(void)
{
    sdio_unregister_driver(&sdio_artosyn_driver);
}

void sdio_stats_print(void)
{
    AR_DBG(DM_SDIO, DL_MSG, "total_tx_pkts %llu total_tx_bytes %llu\n", total_tx_pkts, total_tx_bytes);
    AR_DBG(DM_SDIO, DL_MSG, "total_rx_pkts %llu total_rx_bytes %llu\n", total_rx_pkts, total_rx_bytes);
    AR_DBG(DM_SDIO, DL_MSG, "total_rx_isr %llu total_rx_process %llu\n", total_rx_isr, total_rx_process);
    AR_DBG(DM_SDIO, DL_MSG, "total_tx_isr %llu\n", total_tx_isr);
}

module_param(rx_sleep_us, uint, 0);
MODULE_PARM_DESC(rx_sleep_us, "rx sleep time(unit:us)");

module_param(tx_sleep_us, uint, 0);
MODULE_PARM_DESC(tx_sleep_us, "tx sleep time(unit:us)");

module_param(rom_mode_test, bool, 0);
MODULE_PARM_DESC(rom_mode_test, "Whether com with rom mode");

module_param(sdio_rx_tp_test, bool, 0);
MODULE_PARM_DESC(sdio_rx_tp_test, "rx tp test mode");

module_param(sdio_tx_tp_test, bool, 0);
MODULE_PARM_DESC(sdio_tx_tp_test, "tx tp test mode");

module_param(sdio_tx_tp_test_len, uint, 0);
MODULE_PARM_DESC(sdio_tx_tp_test_len, " tx TP test buf len(bytes)");